package com.spring.mvc.servlet.version_02;

import com.spring.mvc.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: WuPeng
 * @Date: 2021/6/5 5:43 下午
 */
public class IDispatcherServlet extends HttpServlet {

    // 保存用户的配置信息
    private Properties CONTEXT_CONFIG = new Properties();
    // 缓存包路径下扫描到的全限定路径类名
    private List<String> CLASS_NAMES = new ArrayList<>();
    // IOC容器，beanName --> bean instance，扫描到的所有类的实例
    private Map<String, Object> BEANS_MAP = new ConcurrentHashMap<>();
    // Controller中URL与Method的对应关系
    private Map<String, Method> HANDLER_MAPPING = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            this.doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception, Detail: " + Arrays.toString(e.getStackTrace()));
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件
        loadConfig(config.getInitParameter("contextConfigLocation"));
        //2、扫描相关的类
        doScanner(CONTEXT_CONFIG.getProperty("scanPackage"));
        //3、初始化IOC容器，扫描到的类进行实例化，并缓存到IOC容器中
        doInstance();
        //4、完成属性注入（DI 依赖注入）
        doAutowire();
        //5、MVC功能，初始化HandlerMapping
        doInitHandlerMapping();

        System.out.println("WuPeng's SpringFramework has been initialized ！");
    }

    private void doInitHandlerMapping() {
        if (BEANS_MAP.isEmpty()) return;
        for (Map.Entry<String, Object> entry : BEANS_MAP.entrySet()){
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(IController.class)) continue;

            String base_path = "";
            if (clazz.isAnnotationPresent(IRequestMapping.class))
                base_path = "/" + clazz.getAnnotation(IRequestMapping.class).value();

            for (Method method : clazz.getMethods()){
                if (!method.isAnnotationPresent(IRequestMapping.class)) continue;
                IRequestMapping requestMapping = method.getAnnotation(IRequestMapping.class);
                String url = (base_path + "/" + requestMapping.value()).replaceAll("/+", "/");
                HANDLER_MAPPING.put(url, method);
                System.out.println("Mapped: " + url + "-->" + method);
            }
        }
    }

    private void doAutowire() {
        if (BEANS_MAP.isEmpty()) return;
        for (Map.Entry<String, Object> entry : BEANS_MAP.entrySet()){
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields){
                if (!field.isAnnotationPresent(IAutowired.class)) continue;
                IAutowired iAutowired = field.getAnnotation(IAutowired.class);
                String beanName = iAutowired.value().trim();
                if ("".equals(beanName))
                    beanName = field.getType().getName();
                field.setAccessible(true);
                try {
                    //相当于 demoController.demoService = ioc.get("com.spring.mvc.web_demo.service.IDemoService");
                    field.set(entry.getValue(), BEANS_MAP.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doInstance() {
        if (CLASS_NAMES.isEmpty()) return;
        try {
            for (String className : CLASS_NAMES){
                Class<?> clazz = Class.forName(className);
                // 注解信息解析
                if (clazz.isAnnotationPresent(IController.class)){
                    // 1、类名首字母小写作为bean name
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    BEANS_MAP.put(beanName, clazz.newInstance());
                }
                else if (clazz.isAnnotationPresent(IService.class)){
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    // 2、同名类名，优先使用别名（自定义bean名称）
                    IService iService = clazz.getAnnotation(IService.class);
                    if (!"".equals(iService.value())){
                        beanName = iService.value();
                    }
                    BEANS_MAP.put(beanName, clazz.newInstance());
                    // 3、接口的处理
                    for (Class ins : clazz.getInterfaces()){
                        if (BEANS_MAP.containsKey(ins.getName()))
                            throw new Exception("bean name " + ins.getName() + " is exists.");
                        BEANS_MAP.put(ins.getName(), clazz.newInstance());
                    }
                }
                else continue;
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    private String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        //利用了ASCII码，大写字母和小写相差32这个规律
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File resource = new File(url.getFile());
        File[] files = resource.listFiles();
        for (File file : files){
            if (file.isDirectory())
                doScanner(scanPackage + "." + file.getName());
            else {
                if (!file.getName().endsWith(".class")) continue;
                // 包含包名的全限定类名
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                // 添加到容器缓存中，方便下一步对类的实例化
                CLASS_NAMES.add(className);
            }
        }
    }

    // 根据contextConfigLocation路径到classpath下找到对应的配置文件，并加载为配置信息类Properties
    private void loadConfig(String contextConfigLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            CONTEXT_CONFIG.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    // 根据URL委派给具体的调用方法
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

        if (!this.HANDLER_MAPPING.containsKey(url)){
            resp.getWriter().write("404 Not Found !");
            return;
        }

        Method method = this.HANDLER_MAPPING.get(url);
        Object[] paramValues = analysisParameter(method, req, resp);
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(BEANS_MAP.get(beanName), paramValues);
    }

    //get请求参数解析
    private Object[] analysisParameter(Method method, HttpServletRequest req, HttpServletResponse resp) {
        //形参位置与名称映射关系
        Map<String, Integer> paramIndexMapping = new HashMap<>();
        Annotation[][] pa = method.getParameterAnnotations();
        for (int i=0; i<pa.length; i++){
            for (Annotation an : pa[i]){
                if (an instanceof IRequestParam){
                    String paramName = ((IRequestParam) an).value();
                    if (!"".equals(paramName.trim())){
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
        }
        //形参数据类型, HttpServletRequest、HttpServletResponse参数的位置对应关系
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> type = parameterTypes[i];
            if (HttpServletRequest.class == type || HttpServletResponse.class == type){
                paramIndexMapping.put(type.getName(), i);
            }
        }
        //根据参数位置匹配参数名称，从请求的URL中取到参数名称对应的值
        Object[] paramValues = new Object[parameterTypes.length];
        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()){
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", "");
            if (!paramIndexMapping.containsKey(param.getKey())) continue;
            int index = paramIndexMapping.get(param.getKey());
            paramValues[index] = value;
        }
        if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int index = paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }
        if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int index = paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }
        return paramValues;
    }
}
